-
Notifications
You must be signed in to change notification settings - Fork 95
Avoiding redundant mounts by using /proc/mount info #1224
Conversation
For the repeating code between photon and vmdk driver: #1212 |
1. Convert a short volume name to the long one. If required to a trip to vmdkops service 2. In every mount request, check if the volume is already mounted (info of /proc/mount) 3. refcount test which kills docker and verifies refcounting Fixes #1220
943ae20
to
be205c1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workflow and code look clean. A few comments inside, main ones being (1) please avoid cut-n-paste, this makes it hard to change/track code (2) no need to do volume.Get if Mount() already getting full volume name.
A few more small comments insides. All in all - good changed and fast delivery, thanks !
@@ -54,6 +55,8 @@ const ( | |||
fsTypeTag = "Fs_Type" | |||
) | |||
|
|||
var fullVolumeNameMap map[string]string // save full volume names to avoid trip to vmdkops service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is 'mount id -> fullVOlumeName', right ? Or is is something else
Please add comment ("mountId->fullVolumeName map" or whatever it is)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rename fullVolumeNameMap to mountIdMap.
Should this be member of VolumeDriver since it is a property of volumeDriver?
@@ -362,6 +367,19 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead | |||
|
|||
// private function that does the job of mounting volume in conjunction with refcounting | |||
func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { | |||
// get volume metadata | |||
status, err := d.GetVolume(r.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you need GetVolume on short names only, so the check for IsFullVolumeName should happen before the GetVolume .
I think the algorithm is supposed to be as follows (for mount , in pseudocode)
if IsFullVolumeName(name) {
name = r.Name
} else {
s.GetVolume () //get volume info
name = FormFullVolumeName (r.Name, GetDatastore(result_of_volume_get)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In mount, we need GetVolume always, since the volume meta data is used below for fstype and access type.
@@ -618,6 +633,16 @@ func (d *VolumeDriver) Unmount(r volume.UnmountRequest) volume.Response { | |||
return volume.Response{Err: ""} | |||
} | |||
|
|||
if plugin_utils.IsFullVolumeName(r.Name) != true { | |||
fullVolName := fullVolumeNameMap[r.ID] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this is the right way of doing things. mount()
should be similar
if plugin_utils.IsFullVolumeName(r.Name) != true { | ||
fullVolName := fullVolumeNameMap[r.ID] | ||
if fullVolName == "" { // if ID not present in local map, do a get trip | ||
status, _ := d.GetVolume(r.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why no check for GetVolume failure ?
fullVolName = plugin_utils.GetFullVolumeName(r.Name, datastore) | ||
} | ||
r.Name = fullVolName | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to wrap up L639->644 (with slight modification) in COnstructFullVolumeName (shortName string) string
function returning full name (and error) . Otherwise cut-n-paste is on the way
@@ -458,6 +477,16 @@ func (d *VolumeDriver) Unmount(r volume.UnmountRequest) volume.Response { | |||
return volume.Response{Err: ""} | |||
} | |||
|
|||
if plugin_utils.IsFullVolumeName(r.Name) != true { | |||
fullVolName := fullVolumeNameMap[r.ID] | |||
if fullVolName == "" { // if ID not present in local map, do a get trip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I saw this code a few time already :-).
Please refactor to avoid cut-n-paste
) | ||
|
||
const ( | ||
linuxMountsFile = "/proc/mounts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was already defined somewhere. Did you fully move the definition here ? If not, please do
|
||
// GetMountInfo - return a map of mounted volumes and devices | ||
func GetMountInfo(mountRoot string) (map[string]string, error) { | ||
volumeMap := make(map[string]string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment of what is mapped to what (even if it's an old code). Something like // maps volume->fullMountPath
or whatever it is doing
vmdk_plugin/utils/refcount/refcnt.go
Outdated
r.Incr(mount.Name) | ||
log.Infof(" name=%v (driver=%s source=%s) (%v)", | ||
// check if the mount location belongs to vmdk plugin | ||
if matchNameforVMDK(mount.Source) != true { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the comment is better than the function name. It would be nice to name it isVMDKMount() or something like that. matchNameforVMDK
is confusing
vmdk_plugin/utils/refcount/refcnt.go
Outdated
volname := mount.Name | ||
if plugin_utils.IsFullVolumeName(volname) != true { | ||
if datastore == "" { | ||
volumeMeta, _ := d.GetVolume(volname) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and I certainly saw this code already. Please refactor to avoid cut-n-paste.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Please check comments inline.
@@ -54,6 +55,8 @@ const ( | |||
fsTypeTag = "Fs_Type" | |||
) | |||
|
|||
var fullVolumeNameMap map[string]string // save full volume names to avoid trip to vmdkops service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rename fullVolumeNameMap to mountIdMap.
Should this be member of VolumeDriver since it is a property of volumeDriver?
@@ -362,6 +367,19 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead | |||
|
|||
// private function that does the job of mounting volume in conjunction with refcounting | |||
func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { | |||
// get volume metadata | |||
status, err := d.GetVolume(r.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many tiny helper functions. How about;
getFullVolumeName(name) {
if (strings.ContainsAny(name, "@")) {
return name;
}
volMeta, err := d.GetVolume(name)
if err != nil
return strings.Join(name, "@", volumeMeta["datastore"])
else
???? return err?
}
@@ -618,6 +633,16 @@ func (d *VolumeDriver) Unmount(r volume.UnmountRequest) volume.Response { | |||
return volume.Response{Err: ""} | |||
} | |||
|
|||
if plugin_utils.IsFullVolumeName(r.Name) != true { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is unnecessary. fullVolumeNameMap[r.ID] if exists will have fullVolName
if plugin_utils.IsFullVolumeName(r.Name) != true { | ||
fullVolName := fullVolumeNameMap[r.ID] | ||
if fullVolName == "" { // if ID not present in local map, do a get trip | ||
status, _ := d.GetVolume(r.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
status => volMeta ?
} | ||
r.Name = fullVolName | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dont you have to delete mountID => volName entry from map?
echo "Checking recovery through docker kill" | ||
# kill docker daemon forcefully | ||
pkill -9 dockerd | ||
until pids=$(pidof dockerd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this impact the refcount module. Unless the plugin is restarted, the refcount module hasn't lost it's refcounts to do a recovery.
sync # give log the time to flush | ||
wait_for check_recovery_record $timeout | ||
if [ "$?" -ne 0 ] ; then | ||
echo PLUGIN RESTART TEST FAILED. Did not find proper recovery record | ||
echo DOCKER RESTART TEST FAILED. Did not find proper recovery record |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this change is needed. The test is to see if the plugin restarts then the refcounts are restored. If the plugin restart isn't possible then this test should be made manual to execute with a standalone binary and deprecated from automated test run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test will in turn test plugin restart (docker restart). Plugin restart through docker restart tests the docker api consumption, timeout and handling of parallel mounts/unmounts till refcounting is successful. Not sure why not have this as automated test?
c89cae0
to
e88907c
Compare
vmdk_plugin/utils/refcount/refcnt.go
Outdated
} | ||
volname := mount.Name | ||
// gets hit once to retrieve the default datastore. datastoreName is reused henceforth | ||
if datastoreName == "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is done once, but needs to be done inside the loop since we need a volume name to get the datastore.
@msterin @pdhamdhere I have addressed your comments in the latest commit. Please take a look. |
@@ -362,6 +365,19 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead | |||
|
|||
// private function that does the job of mounting volume in conjunction with refcounting | |||
func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { | |||
// get volume metadata | |||
volumeMeta, err := d.GetVolume(r.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If plugin_utils.AlreadyMounted(r.Name, d.mountRoot)
then you can skip d.GetVolume(r.Name)
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
saving VolumeMeta as part of retrieving full volume name. If found, avoiding next get trip.
// get volume metadata if required | ||
if volumeMeta == nil { | ||
if volumeMeta, err = d.ops.Get(r.Name); err != nil { | ||
return volume.Response{Err: err.Error()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dont you have to decrRefCount?
} | ||
|
||
// GetFullNameAndMeta - return a qualified volume and metadata(if a get trip was made) | ||
func GetFullNameAndMeta(name string, datastoreName string, d drivers.VolumeDriver) (string, map[string]interface{}, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not go expert and don't know what is recommended practice here. It definitely seems inconsistent with rest of the code which generally returns "retValue, err" tuple. Should we change it to GetVolumeInfo to return dictionary "volInfo, err" tuple where volInfo.fullName and volInfo.metadata has required details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also suggested GetVolumeInfo
. We can leave it with 3 ret vals but need to accurately document IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. wrapping this in volInfo to contain. It will make code cleaner.
@@ -360,8 +363,21 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead | |||
return mountpoint, fs.MountWithID(mountpoint, fstype, id, isReadOnly) | |||
} | |||
|
|||
//VolumesInRefMap - get list of volumes from refmap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: space between // and VolumesInRefMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another nit - please mention in the comments that these are full names volume@datastore format
if !exists { | ||
fstype = fs.FstypeDefault | ||
} | ||
|
||
skipAttach := false | ||
// If the volume is already attached to the VM, skip the attach. | ||
if state, stateExists := status["State"]; stateExists { | ||
if state, stateExists := volumeMeta["State"]; stateExists { | ||
if strings.Compare(state.(string), "DETACHED") != 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not your change but please file an issue and let's revisit.
Generally we don't make decision based on data in KV file and definitely not inside guest. Disk may be attached to some other VM. If neither refCnt nor mount map suggest disk is attached to this VM then we must issue attach to ESX. If it is already attached, it is a no-op from ESX perspective anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. A bunch of small nits (comments and spaces mainly) and only 1 comment - I think @
split/join should be wrapped in a simple interface for volumes names (volname.join, volname.split, etc). And then it is good to go IMO
@@ -360,8 +363,21 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead | |||
return mountpoint, fs.MountWithID(mountpoint, fstype, id, isReadOnly) | |||
} | |||
|
|||
//VolumesInRefMap - get list of volumes from refmap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another nit - please mention in the comments that these are full names volume@datastore format
} | ||
|
||
r.Name, err = plugin_utils.GetFullVolumeName(r.Name, volumeMeta["datastore"].(string), d) | ||
volname, volumeMeta, err := plugin_utils.GetFullNameAndMeta(r.Name, "", d) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the name is accurate but confusing IMO. I'd simply call it GetVolInfo() and added comment
//returns fullName. volumeMeta map and err if any
if err != nil { | ||
log.Errorf("Unable to get full name for volume %s. err:%v", r.Name, err) | ||
return volume.Response{Err: err.Error()} | ||
} | ||
r.Name = volname | ||
d.mountIDtoName[r.ID] = volname |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - I'd use r.Name here, not volname (just like everywhere till the rest of the function)
if err != nil { | ||
log.Errorf("Unable to get full name for volume %s. err:%v", r.Name, err) | ||
return volume.Response{Err: err.Error()} | ||
} | ||
r.Name = volname | ||
d.mountIDtoName[r.ID] = volname |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same nit - (use r.Name not volname., as everyone till the end of this function)
} | ||
return volumeMeta["datastore"].(string), nil | ||
return volumeMeta["datastore"].(string), volumeMeta, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - in other places volumeMeta is indexed by a named constant, not literal. Why sudden change ?
} | ||
|
||
// GetFullVolumeName - append datastore to the volume name | ||
func GetFullVolumeName(name string, datastoreName string, d drivers.VolumeDriver) (string, error) { | ||
// GetNameFromRefmap - get names from refmap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please add comment. Something like "finds an entry for volume. Entry can be in vol@ds format, Volums is passed in as a short name, with no '@ds' (btw 0 is this what you intended to do ? )
} | ||
count++ | ||
fullname = name | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this one scans the full list regardess. Generally not nice, but I guess these lists are short...
} | ||
|
||
// GetFullNameAndMeta - return a qualified volume and metadata(if a get trip was made) | ||
func GetFullNameAndMeta(name string, datastoreName string, d drivers.VolumeDriver) (string, map[string]interface{}, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also suggested GetVolumeInfo
. We can leave it with 3 ret vals but need to accurately document IMO
} | ||
if datastoreName != "" { | ||
return strings.Join([]string{name, datastoreName}, "@"), nil | ||
return strings.Join([]string{name, datastoreName}, "@"), nil, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not like the joins and splits sprinkled over the code.
Can it we wrapped in a couple of fucntions (similar to python os.path.join and os.path,basename) ?
vmdk_plugin/utils/refcount/refcnt.go
Outdated
@@ -264,6 +264,19 @@ func (r *RefCountsMap) GetCount(vol string) uint { | |||
return rc.count | |||
} | |||
|
|||
//GetVolumeNames - return volume names of entries in refmap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space after //.
Also - full names ? Where do we use a full list of volnames ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Just one additional optimization to get full volume name from entries in refmap(if there no multiple entries for given volume name) and avoid a trip to esx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// GetVolumeNames - return fully qualified volume names from refMap
vmdk_plugin/utils/refcount/refcnt.go
Outdated
} | ||
|
||
return volumeList | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly add a test that creates and mounts volumes with the same name on different datastores.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. did you mean "list can contain volumes with same name on different datastores?"
} | ||
|
||
// GetNameFromRefmap - get names from refmap | ||
func GetNameFromRefmap(volName string, d drivers.VolumeDriver) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit - could make this a little descriptive comment especially how volumes with the same name on different datastores are handled.
Plus what happens in that case and this function returns an empty string. Its not wrong to have volumes with the same name on different datastores.
General - Pls. add a descriptive doc update of this change to any existing refcounting doc,
// find full volume names using refmap if possible | ||
if fullVolumeName := GetNameFromRefmap(name, d); fullVolumeName != "" { | ||
return fullVolumeName, nil, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike what the name suggests "GetFullNameAndMeta()" most of the returns here return NULL for the meta. Is that intended? If only one condition is going to return the metadata for a volume then suggest that this function just return the full name or even merge it with the calling function.
Should never mix multiple unrelated actions inside a single function. - doesn't need to return the meta-data for a volume GetVolume() should be used for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes renaming it. The idea was to return additional information along with full name so as to avoid trips to esx
// GetDatastore - get datastore from volume metadata | ||
// Note "datastore" key is defined in vmdkops service | ||
func GetDatastore(name string, d drivers.VolumeDriver) (string, map[string]interface{}, error) { | ||
volumeMeta, err := d.GetVolume(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function is returning values that are not seemingly related to the name of the function - GetDatastore(). Preferably remove this and let the calling function just call GetVolume() and pick the datastore entry from the return meta-data.
If this function is a must-have then let it return just the datastore name. I mean we are returning the datastore name and the meta-data which also has the same datastore name in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored. This function is no more needed. did the getvolume in the calling function.
vmdk_plugin/utils/refcount/refcnt.go
Outdated
volname := mount.Name | ||
// gets hit once to retrieve the default datastore. datastoreName is reused henceforth | ||
if datastoreName == "" { | ||
datastoreName, _, err = plugin_utils.GetDatastore(volname, d) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't needed anymore. returned datastore as part of volumeInfo. captured here to be reused further (and avoid trip to esx)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
} | ||
|
||
// GetVolumeInfo - return VolumeInfo which a qualified volume name, | ||
// datastore name volume metadata if retrieved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about changing comment to following?
return VolumeInfo with a qualified volume name.
Optionally returns datastore and volume metadata if retrieved from ESX.
If Volume Metadata is nil then caller can use getVolume()
vmdk_plugin/utils/refcount/refcnt.go
Outdated
@@ -264,6 +264,19 @@ func (r *RefCountsMap) GetCount(vol string) uint { | |||
return rc.count | |||
} | |||
|
|||
//GetVolumeNames - return volume names of entries in refmap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// GetVolumeNames - return fully qualified volume names from refMap
Fixes #1220
Testing:
The refcount test added kills dockerd and verifies refcount (after refcount has been initialized) through logs.
Verified the same manually by forcefully disabling plugin and enabling it.